// Copyright (c) 2021, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.desugar.desugaredlibrary;

import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexClassAndMethod;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexLibraryClass;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.GenericSignature.ClassTypeSignature;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeterInstructionEventConsumer.DesugaredLibraryRetargeterPostProcessingEventConsumer;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.collections.DexClassAndMethodSet;
import com.google.common.collect.Maps;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;

// The rewrite of virtual calls requires to go through emulate dispatch. This class is responsible
// for inserting interfaces on library boundaries and forwarding methods in the program, and to
// synthesize the interfaces and emulated dispatch classes in the desugared library.
public class DesugaredLibraryRetargeterPostProcessor implements CfPostProcessingDesugaring {

  private final AppView<?> appView;
  private final DesugaredLibraryRetargeterSyntheticHelper syntheticHelper;
  private final DexClassAndMethodSet emulatedDispatchMethods;

  public DesugaredLibraryRetargeterPostProcessor(
      AppView<?> appView, RetargetingInfo retargetingInfo) {
    this.appView = appView;
    this.syntheticHelper = new DesugaredLibraryRetargeterSyntheticHelper(appView);
    emulatedDispatchMethods = retargetingInfo.getEmulatedDispatchMethods();
  }

  @Override
  public void postProcessingDesugaring(CfPostProcessingDesugaringEventConsumer eventConsumer) {
    if (appView.options().isDesugaredLibraryCompilation()) {
      ensureEmulatedDispatchMethodsSynthesized(eventConsumer);
    } else {
      ensureInterfacesAndForwardingMethodsSynthesized(eventConsumer);
    }
  }

  private void ensureInterfacesAndForwardingMethodsSynthesized(
      DesugaredLibraryRetargeterPostProcessingEventConsumer eventConsumer) {
    assert !appView.options().isDesugaredLibraryCompilation();
    Map<DexType, List<DexClassAndMethod>> map = Maps.newIdentityHashMap();
    for (DexClassAndMethod emulatedDispatchMethod : emulatedDispatchMethods) {
      map.putIfAbsent(emulatedDispatchMethod.getHolderType(), new ArrayList<>(1));
      map.get(emulatedDispatchMethod.getHolderType()).add(emulatedDispatchMethod);
    }
    for (DexProgramClass clazz : appView.appInfo().classes()) {
      if (clazz.superType == null) {
        assert clazz.type == appView.dexItemFactory().objectType : clazz.type.toSourceString();
        continue;
      }
      DexClass superclass = appView.definitionFor(clazz.superType);
      // Only performs computation if superclass is a library class, but not object to filter out
      // the most common case.
      if (superclass != null
          && superclass.isLibraryClass()
          && superclass.type != appView.dexItemFactory().objectType) {
        map.forEach(
            (type, methods) -> {
              if (inherit(superclass.asLibraryClass(), type, emulatedDispatchMethods)) {
                ensureInterfacesAndForwardingMethodsSynthesized(eventConsumer, clazz, methods);
              }
            });
      }
    }
  }

  private boolean inherit(
      DexLibraryClass clazz, DexType typeToInherit, DexClassAndMethodSet retarget) {
    DexLibraryClass current = clazz;
    while (current.type != appView.dexItemFactory().objectType) {
      if (current.type == typeToInherit) {
        return true;
      }
      DexClass dexClass = appView.definitionFor(current.superType);
      if (dexClass == null || dexClass.isClasspathClass()) {
        reportInvalidLibrarySupertype(current, retarget);
        return false;
      } else if (dexClass.isProgramClass()) {
        // If dexClass is a program class, then it is already correctly desugared.
        return false;
      }
      current = dexClass.asLibraryClass();
    }
    return false;
  }

  private void ensureInterfacesAndForwardingMethodsSynthesized(
      DesugaredLibraryRetargeterPostProcessingEventConsumer eventConsumer,
      DexProgramClass clazz,
      List<DexClassAndMethod> methods) {
    // DesugaredLibraryRetargeter emulate dispatch: insertion of a marker interface & forwarding
    // methods.
    // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
    // applies up to 24.
    for (DexClassAndMethod method : methods) {
      DexClass newInterface =
          syntheticHelper.ensureEmulatedInterfaceDispatchMethod(method, eventConsumer);
      if (clazz.interfaces.contains(newInterface.type)) {
        // The class has already been desugared.
        continue;
      }
      clazz.addExtraInterfaces(
          Collections.singletonList(new ClassTypeSignature(newInterface.type)));
      eventConsumer.acceptInterfaceInjection(clazz, newInterface);
      if (clazz.lookupVirtualMethod(method.getReference()) == null) {
        DexEncodedMethod newMethod = createForwardingMethod(method, clazz);
        clazz.addVirtualMethod(newMethod);
        eventConsumer.acceptForwardingMethod(new ProgramMethod(clazz, newMethod));
      }
    }
  }

  private DexEncodedMethod createForwardingMethod(DexClassAndMethod target, DexClass clazz) {
    // NOTE: Never add a forwarding method to methods of classes unknown or coming from
    // android.jar
    // even if this results in invalid code, these classes are never desugared.
    // In desugared library, emulated interface methods can be overridden by retarget lib members.
    DexMethod forwardMethod =
        appView.options().desugaredLibraryConfiguration.retargetMethod(target, appView);
    assert forwardMethod != null && forwardMethod != target.getReference();
    DexEncodedMethod desugaringForwardingMethod =
        DexEncodedMethod.createDesugaringForwardingMethod(
            target, clazz, forwardMethod, appView.dexItemFactory());
    desugaringForwardingMethod.setLibraryMethodOverride(OptionalBool.TRUE);
    return desugaringForwardingMethod;
  }

  private void ensureEmulatedDispatchMethodsSynthesized(
      DesugaredLibraryRetargeterInstructionEventConsumer eventConsumer) {
    assert appView.options().isDesugaredLibraryCompilation();
    if (emulatedDispatchMethods.isEmpty()) {
      return;
    }
    for (DexClassAndMethod emulatedDispatchMethod : emulatedDispatchMethods) {
      syntheticHelper.ensureEmulatedHolderDispatchMethod(emulatedDispatchMethod, eventConsumer);
    }
  }

  private void reportInvalidLibrarySupertype(
      DexLibraryClass libraryClass, DexClassAndMethodSet retarget) {
    DexClass dexClass = appView.definitionFor(libraryClass.superType);
    String message;
    if (dexClass == null) {
      message = "missing";
    } else if (dexClass.isClasspathClass()) {
      message = "a classpath class";
    } else {
      message = "INVALID";
      assert false;
    }
    appView
        .options()
        .warningInvalidLibrarySuperclassForDesugar(
            dexClass == null ? libraryClass.getOrigin() : dexClass.getOrigin(),
            libraryClass.type,
            libraryClass.superType,
            message,
            retarget);
  }
}
